Design Patterns
contents
디자인 패턴은 소프트웨어 설계에서 흔히 발생하는 문제에 대한 일반적이고 재사용 가능한 해결책입니다. 이는 완성된 코드가 아니라, 특정 문제를 해결하기 위해 따를 수 있는 템플릿 또는 "청사진" 과 같습니다.
이렇게 생각해 보세요. 디자인 패턴은 주방의 청사진과 같습니다. 벽을 무슨 색으로 칠할지 알려주지는 않지만, 싱크대, 오븐, 냉장고를 효율적이고 기능적으로 배치하는 검증된 레이아웃을 설명합니다.
이 패턴들은 "GoF(Gang of Four)" 라고 알려진 에릭 감마, 리처드 헬름, 랄프 존슨, 존 블리시디스가 쓴 1994년 책 Design Patterns: Elements of Reusable Object-Oriented Software 를 통해 유명해졌습니다.
디자인 패턴을 사용하는 이유
디자인 패턴을 사용하면 다음과 같은 주요 이점이 있습니다.
- 공유된 어휘: "여기서 스트래티지 패턴을 사용합시다"라고 말하면, 팀의 모든 개발자가 당신이 제안하는 전체 설계를 즉시 이해합니다. 복잡한 아이디어를 정확하고 효율적으로 전달하는 방법입니다.
- 검증된 해결책: 이 패턴들은 수년에 걸쳐 수천 명의 개발자에 의해 개발되고, 테스트되고, 검증된 해결책입니다. "바퀴를 다시 발명"(어쩌면 네모난 바퀴를 발명)하는 수고를 덜어주며, 신뢰성이 검증되었습니다.
- 유연성 및 유지보수성: 디자인 패턴으로 구축된 코드는 종종 더 유연하고, 모듈화되어 있으며, 유지보수하기 쉽습니다. 많은 패턴이 의존성을 줄이고 나중에 새로운 기능을 변경하거나 추가하기 쉽게 설계되었습니다.
디자인 패턴의 세 가지 범주
23개의 고전적인 GoF 패턴은 목적에 따라 세 가지 주요 범주로 나뉩니다.
- 생성 패턴 (Creational Patterns): 객체 생성 메커니즘을 다룹니다.
- 구조 패턴 (Structural Patterns): 객체와 클래스를 더 큰 구조로 조합하는 방법을 설명합니다.
- 행동 패턴 (Behavioral Patterns): 알고리즘과 객체 간의 책임 할당에 관련됩니다.
1. 🎨 생성 패턴
이 패턴들은 기존 코드의 유연성과 재사용성을 높이는 객체 생성 메커니즘을 제공합니다. 시스템이 객체를 생성하고, 구성하고, 표현하는 방식에 독립적이 되도록 돕습니다.
예제 1: 싱글톤 패턴 (Singleton Pattern)
- 정의: 클래스가 단 하나의 인스턴스만 갖도록 보장하고, 이에 대한 단일하고 전역적인 접근점을 제공합니다.
- 비유: 한 나라의 대통령이나 중앙 은행과 같습니다. 한 번에 한 명(곳)만 존재할 수 있으며, 모든 사람은 그들에게 접근하기 위해 "공식적인" 채널을 거쳐야 합니다.
- 사용 시기: 로거, 설정 관리자, 데이터베이스 커넥션 풀과 같이 애플리케이션 전체에서 공유되어야 하는 구성 요소에 사용합니다.
예제 2: 팩토리 메서드 패턴 (Factory Method Pattern)
- 정의: 객체를 생성하기 위한 인터페이스를 정의하지만, 어떤 클래스를 인스턴스화할지는 서브클래스가 결정하도록 합니다. "팩토리 메서드"는 클래스가 인스턴스화를 자식 클래스에게 미루도록 합니다.
- 비유: 피자 프랜차이즈. 본사(
PizzaShop)는createPizza()메서드를 정의합니다.NYPizzaStore는 이 메서드를 구현하여NYStylePizza를 반환하고,ChicagoPizzaStore는ChicagoStylePizza를 반환합니다. - 사용 시기: 정확히 어떤 클래스를 생성해야 할지 미리 알 수 없을 때 사용합니다. 메인 코드를 생성해야 하는 특정 객체 타입으로부터 분리(decoupling)하는 데 좋습니다.
2. 🏗️ 구조 패턴
이 패턴들은 구조를 유연하고 효율적으로 유지하면서 객체와 클래스를 더 큰 구조로 조합하는 방법을 설명합니다.
예제 1: 어댑터 패턴 (Adapter Pattern)
- 정의: 번역기처럼 작동하여, 호환되지 않는 두 인터페이스가 함께 작동할 수 있도록 합니다. 한 클래스의 인터페이스를 클라이언트가 기대하는 인터페이스로 "변환(adapt)"합니다.
- 비유: 여행용 전원 어댑터. 미국 플러그가 있는 노트북 충전기는 유럽형 콘센트에 맞지 않습니다. 어댑터가 중간에 끼어들어 유럽 스타일의 전원을 받아 미국 스타일의 소켓을 제공합니다.
- 사용 시기: 통합에 필수적입니다. 서드파티 라이브러리나 레거시 클래스의 소스 코드를 변경하지 않고도 나머지 시스템과 작동하게 만들어야 할 때 사용합니다.
예제 2: 데코레이터 패턴 (Decorator Pattern)
- 정의: 객체를 다른 객체로 "감싸서(wrapping)" 동적으로 새로운 행동이나 책임을 첨부할 수 있게 합니다.
- 비유: 커피 주문하기. 기본
SimpleCoffee객체에서 시작합니다. 그다음 50센트를 추가하는WithMilk데코레이터로 "장식"하고, 그다음 25센트를 추가하는WithSugar데코레이터로 장식합니다. 각 데코레이터는 이전 데코레이터를 감싸고 자신만의 비용과 설명을 추가합니다. - 사용 시기: 상속을 사용하지 않고 객체에 기능을 추가하고 싶을 때 사용합니다. 서브클래싱(subclassing)에 대한 유연한 대안입니다. 자바의 I/O 클래스(
BufferedReader(new FileReader(...)))가 고전적인 예입니다.
3. 🏃♂️ 행동 패턴
이 패턴들은 객체 간의 커뮤니케이션에 관한 것입니다. 공통적인 커뮤니케이션 패턴을 식별하고 이를 더 유연하고 신뢰할 수 있게 만듭니다.
예제 1: 스트래티지 패턴 (Strategy Pattern)
- 정의: 알고리즘 제품군을 정의하고, 각각을 별도의 클래스로 캡슐화하며, 상호 교체할 수 있도록 만듭니다. 이를 통해 알고리즘이 그것을 사용하는 클라이언트와 독립적으로 변경될 수 있습니다.
- 비유: 구글 맵과 같은 내비게이션 앱. 메인 애플리케이션("컨텍스트")은
Maps()메서드를 가지고 있습니다. 여기에DrivingStrategy,WalkingStrategy,BikingStrategy와 같은 다양한 전략을 제공할 수 있습니다. 메인 앱은 경로가 어떻게 계산되는지는 신경 쓰지 않고, 선택된 전략 객체에 작업을 위임할 뿐입니다. - 사용 시기: 긴
if/else나switch문을 제거하고 싶을 때 사용합니다. 여러 가지 방법으로 수행할 수 있는 작업이 있을 때, 런타임에 그 "방법"을 선택하기 위해 스트래티지 패턴을 사용합니다.
예제 2: 옵저버 패턴 (Observer Pattern)
- 정의: 객체 간에 일대다(one-to-many) 의존성을 정의합니다. 한 객체("주체", subject)의 상태가 변하면, 그 객체에 의존하는 모든 객체("관찰자", observer)들이 자동으로 알림을 받고 업데이트됩니다.
- 비유: 잡지 구독. 발행인(주체)은 구독자(관찰자)가 누구 인지 모릅니다. 그저 새 호를 발행할 뿐입니다. 구독 서비스는 자동으로 등록된 모든 구독자에게 알림을 보내고 새 호를 배달합니다.
- 사용 시기: 이벤트 기반 프로그래밍의 기초입니다. GUI 이벤트 리스너(버튼 클릭 등)부터 데이터가 변경될 때 UI를 업데이트하는 상태 관리 프레임워크(Redux, Vuex 등)까지 모든 곳에서 사용됩니다.
마지막 경고: "망치와 못" 문제
디자인 패턴은 강력하지만 마법의 총알은 아닙니다. 신입 개발자가 저지르는 흔한 실수는 패턴을 하나 배운 뒤, 자신이 마주하는 모든 문제에 그 패턴을 강제로 적용하려는 것입니다. "손에 망치를 들면 모든 것이 못으로 보인다"는 문제입니다.
항상 가능한 가장 단순한 해결책으로 시작하세요. 디자인 패턴은 오직 그 패턴이 해결하도록 설계된, 특정하고 반복적인 문제가 발생했을 때만 도입해야 합니다.
생성 패턴은 객체 생성 메커니즘을 다루는 소프트웨어 디자인 패턴의 한 범주입니다. 이 패턴의 주된 목표는 시스템이 객체를 생성하고, 구성하며, 표현하는 방식에 독립적이면서 더 유연해지도록 만드는 것입니다.
new 키워드로 특정 클래스를 하드코딩하는 대신, 이 패턴들은 애플리케이션이 필요로 하는 객체를 "제조"하기 위한 더 제어되고 분리된 방법을 제공합니다.
생성 패턴이 해결하는 핵심 문제
직접적인 객체 생성(new MyClass())의 주된 문제는 유연성 부족입니다. 이는 코드를 특정 구상(concrete) 클래스에 강하게 결합시킵니다. 나중에 클래스를 변경(예: MyBetterClass 사용)하고 싶다면, new MyClass()가 사용된 모든 곳을 찾아서 변경해야 합니다.
생성 패턴은 생성 과정을 추상화하여 이 문제를 해결합니다. 코드는 더 이상 "나는 new Truck()이 필요해"라고 말하는 대신, "나는 Vehicle이 필요해"라고 말하고, 패턴이 어떤 특정 Vehicle 타입을 제공할지 결정합니다.
5가지 "GoF" 생성 패턴
가장 유명한 생성 패턴은 "Gang of Four"(GoF)에 의해 설명된 5가지입니다.
1. 팩토리 메서드 패턴 (Factory Method Pattern)
- 개념: 객체를 생성하기 위한 인터페이스("메서드")를 정의하지만, 어떤 클래스를 인스턴스화할지는 서브클래스가 결정하도록 합니다.
- 비유: 피자 배달 프랜차이즈. 부모
PizzaStore클래스는 추상createPizza()메서드를 가집니다.NYPizzaStore(서브클래스)는 이 메서드를 구현하여NYStylePizza객체를 반환하고,ChicagoPizzaStore(다른 서브클래스)는ChicagoStylePizza객체를 반환합니다. 주 주문 과정은 동일하지만, 구체적인 제품은 서브클래스에 의해 결정됩니다. - 사용 시기: 어떤 타입의 객체를 생성해야 할지 클래스가 미리 예측할 수 없을 때, 그 책임을 서브클래스에게 위임하기 위해 사용합니다.
2. 추상 팩토리 패턴 (Abstract Factory Pattern)
- 개념: 구체적인 클래스를 지정하지 않고 관련된 객체들의 집합 을 생성하기 위한 인터페이스를 제공합니다. "팩토리의 팩토리"와 같습니다.
- 비유: 가구 공장. "모던" 또는 "빅토리아"와 같은 스타일(추상 팩토리)을 선택할 수 있습니다.
ModernFurnitureFactory를 선택하면ModernChair,ModernSofa,ModernTable을 얻습니다.VictorianFurnitureFactory를 선택하면VictorianChair,VictorianSofa,VictorianTable을 얻습니다. 메인 코드는 팩토리에 "의자 하나 줘"라고 요청하기만 하면 올바른 스타일의 의자를 받습니다. - 사용 시기: 시스템이 여러 관련 제품군 중 하나로 구성되어야 할 때(예: UI 테마, 데이터베이스 제공자) 사용합니다.
3. 빌더 패턴 (Builder Pattern)
- 개념: 복잡한 객체의 생성 과정과 최종 표현을 분리하여, 객체를 단계별로 만들 수 있게 합니다.
- 비유: 델리에서 맞춤 샌드위치 주문하기. 12가지 재료를 하나의 복잡한 주문서로 한 번에 제공하는 대신, 빌더(직원)에게 단계별로 지시합니다. "밀빵으로 시작해주세요", "터키 추가하고", "프로볼로네 치즈 올려주세요", "양파는 빼주세요." 마지막으로
build()("다 됐어요")를 호출하면, 빌더가 완성된 샌드위치를 줍니다. - 사용 시기: 객체가 많은 (종종 선택적인) 설정 파라미터를 가질 때 발생하는 "점층적 생성자(telescoping constructor)" 문제를 해결하기 위해 사용합니다. 복잡한 객체의 생성을 훨씬 더 읽기 쉽고 오류가 적게 만듭니다.
4. 싱글톤 패턴 (Singleton Pattern)
- 개념: 클래스가 단 하나의 인스턴스만 갖도록 보장하고, 그 인스턴스에 대한 단일하고 전역적인 접근점을 제공합니다.
- 비유: 한 국가의 정부. 중앙 정부는 오직 하나만 존재할 수 있으며, 국가의 모든 부분은 그 단일하고 공유된 실체와 상호작용해야 합니다.
- 사용 시기: 로거, 설정 관리자, 하드웨어 드라이버 또는 데이터베이스 커넥션 풀과 같이 애플리케이션 전체에서 공유되고 조정되어야 하는 객체에 사용합니다.
5. 프로토타입 패턴 (Prototype Pattern)
- 개념: 객체를 처음부터 생성하는 대신, 기존 객체("프로토타입")를 _복제_하여 새 객체를 만듭니다.
- 비유: 세포 분열(체세포 분열). 새로운 동일한 세포를 얻기 위해, 기존 세포가 분열하여 자신을 복제합니다. 이는 원재료로부터 새 세포를 만드는 것보다 훨씬 빠릅니다.
- 사용 시기: 객체 생성 비용이 매우 높을 때(예: 데이터베이스 호출이나 복잡하고 느린 계산이 필요할 때) 사용합니다. 하나의 "마스터" 객체를 만들어 두고, 새로운 유사한 객체가 필요할 때마다 복제하는 것이 더 효율적입니다.
구조 패턴은 객체와 클래스를 더 큰 구조로 조합하는 방법을 설명하며, 이 구조를 유연하고 효율적으로 유지합니다. 이 패턴들은 엔티티 간의 관계를 단순화하여 이들이 함께 작동하기 쉽도록 하는 데 중점을 둡니다.
주요 목표는 의존성을 관리하고 객체를 구성하여, 시스템을 사용하는 부분을 망가뜨리지 않고도 구조를 변경할 수 있도록 하는 것입니다.
1. 어댑터 패턴 (Adapter Pattern) 🔌
어댑터 패턴은 번역기처럼 작동하여, 호환되지 않는 두 인터페이스가 함께 작동할 수 있도록 합니다.
- 핵심 아이디어: 한 클래스의 인터페이스를 클라이언트가 기대하는 인터페이스로 "변환(adapt)"합니다. 기존 클래스를 새로운 인터페이스로 감쌉니다.
- 비유: 여행용 전원 어댑터. 미국 플러그가 있는 노트북 충전기는 유럽형 콘센트에 맞지 않습니다. 어댑터가 중간에 끼어들어 유럽 스타일의 전원을 받아 미국 스타일의 소켓을 제공합니다.
- 사용 시기: 인터페이스가 애플리케이션이 기대하는 것과 일치하지 않는 서드파티 라이브러리나 레거시 클래스를 사용해야 할 때. 라이브러리를 변경할 수 없으므로, "어댑터"를 사용합니다.
2. 데코레이터 패턴 (Decorator Pattern)
데코레이터 패턴은 객체를 특별한 "래퍼(wrapper)" 객체로 감싸서 동적으로 새로운 행동을 첨부할 수 있게 합니다.
- 핵심 아이디어: 기본 객체에서 시작하여 새로운 기능으로 "장식(decorate)"합니다. 여러 데코레이터를 쌓을 수 있습니다.
- 비유: 커피 주문하기. 기본
SimpleCoffee객체로 시작합니다. 그다음WithMilk데코레이터(500원 추가)로 감싸고, 그다음WithSugar데코레이터(250원 추가)로 감쌉니다. 각 데코레이터는 이전 데코레이터를 감싸고 자신만의 비용과 설명을 추가합니다. - 사용 시기: 상속을 사용하지 않고 객체에 기능을 추가하고 싶을 때. 서브클래싱에 대한 유연한 대안입니다. 자바의 I/O 클래스(
BufferedReader(new FileReader(...)))가 고전적인 예입니다.
3. 퍼사드 패턴 (Facade Pattern) 🏛️
퍼사드 패턴은 (서브시스템이나 라이브러리처럼) 크고 복잡한 코드 덩어리에 대해 단순화된, 상위 수준의 인터페이스를 제공합니다.
- 핵심 아이디어: 시스템의 복잡성을 단순한 "전면(front-facing)" 객체 뒤로 숨깁니다.
- 비유: 자동차의 "시동" 버튼. 버튼을 누르면 연료 분사기, 스파크 플러그, 스타터 모터, 엔진 컴퓨터가 모두 함께 작동하는 복잡한 시퀀스가 시작됩니다. 운전자로서, 어떻게 작동하는지 알 필요 없이, 단순한
Start()퍼사드와 상호작용할 뿐입니다. - 사용 시기: 사용하기 어려운 복잡한 서브시스템이 있을 때. 퍼사드는 90%의 사용 사례에 "충분히 좋은" 인터페이스를 제공하며, 필요시 고급 사용자가 복잡한 서브시스템에 직접 접근하는 것도 허용합니다.
4. 컴포지트 패턴 (Composite Pattern) 🌳
컴포지트 패턴은 객체들을 트리 같은 구조로 조합하고, 이 구조들을 마치 개별 객체인 것처럼 다룰 수 있게 합니다.
- 핵심 아이디어: 클라이언트가 개별 객체와 객체의 복합체를 모두 동일하게(uniformly) 다룰 수 있게 합니다.
- 비유: 컴퓨터의 폴더 시스템. "폴더"는 개별 "파일"과 다른 "폴더"를 포함할 수 있고, 그 안의 폴더도 더 많은 파일과 폴더를 포함할 수 있습니다. 단일 파일이나 전체 폴더(그 안의 모든 것에 대해 재귀적으로 작업을 수행)에 대해
getSize()나delete()같은 동일한 작업을 수행할 수 있습니다. 클라이언트는 자신이 단일 항목과 대화하는지 전체 트리와 대화하는지 신경 쓸 필요가 없습니다. - 사용 시기: 부분-전체(part-whole) 계층이나 트리 구조로 작업할 때. 그래픽(
Shape이 단일Circle일 수도,Shape의Group일 수도 있음)이나 UI 프레임워크에서 흔히 사용됩니다.
5. 프록시 패턴 (Proxy Pattern)
프록시 패턴은 다른 객체에 대한 접근을 제어하기 위해 해당 객체를 대신하는 대리자나 플레이스홀더를 제공합니다.
- 핵심 아이디어: 프록시 객체가 "실제" 객체를 대신해 앞에 나섭니다. 클라이언트는 프록시와 대화하고, 프록시는 요청을 실제 "주체" 객체로 전달할지 여부와 방법을 결정합니다.
- 비유: 보안이 철저한 회사 사무실. CEO의 사무실에 그냥 걸어 들어갈 수 없습니다. 먼저 비서(프록시)에게 가야 합니다. 비서는 당신의 신원과 일정을 확인합니다. 모든 것이 정상이면, CEO(실제 주체)를 만날 수 있도록 들여보내 줍니다.
- 사용 시기:
- 가상 프록시: 지연 로딩(Lazy loading)을 위해. 프록시는 "실제" 객체가 실제로 필요할 때만 생성합니다(예: 고해상도 이미지 로딩).
- 보호 프록시: 비유에서처럼 접근 제어를 위해(예: 사용자 권한 확인).
- 원격 프록시: 다른 주소 공간(예: 원격 서버)에 있는 객체를 나타내기 위해.
행동 패턴은 알고리즘과 객체 간의 책임 할당에 중점을 둔 디자인 패턴의 한 범주입니다. 이 패턴들은 서로 다른 객체와 클래스가 작업을 완수하기 위해 어떻게 소통하고 협력하는지 설명합니다.
행동 패턴의 주된 목표는 커뮤니케이션의 유연성을 높이고 객체 간의 상호작용을 더 느슨하게 결합하는 것입니다.
1. 🎯 스트래티지 패턴 (Strategy Pattern)
- 정의: 교체 가능한 알고리즘 제품군을 정의하고, 각 알고리즘을 별도의 클래스로 캡슐화합니다. 이를 통해 런타임에 알고리즘을 선택할 수 있습니다.
- 비유: 내비게이션 앱. 메인 앱("컨텍스트")은 경로를 찾아야 한다는 것은 알지만, 어떻게 찾는지는 모릅니다. 이 작업을 별도의 전략 객체에 위임합니다.
DrivingStrategy(운전 전략),WalkingStrategy(도보 전략),BikingStrategy(자전거 전략) 등을 끼워 넣을 수 있습니다. 메인 앱은 그저strategy.findRoute()를 호출할 뿐, 실제 작업은 선택된 전략 객체가 수행합니다. - 사용 시기: 서로 다른 동작을 선택하는 긴
if/else나switch문을 제거하고 싶을 때 사용합니다. 하나의 작업을 여러 가지 다른 방식으로 수행할 수 있을 때 완벽합니다.
2. 📡 옵저버 패턴 (Observer Pattern)
- 정의: 객체 간에 일대다(one-to-many) 의존성을 정의합니다. 한 객체("주체", Subject)의 상태가 변하면, 그 객체에 의존하는 모든 객체("관찰자", Observer)들이 자동으로 알림을 받고 업데이트됩니다.
- 비유: 잡지 구독. 발행인(주체)은 개별 구독자(관찰자)가 누구인지 알 필요가 없습니다. 그저 새 호를 발행할 뿐입니다. 구독자(관찰자)들은 관심사를 등록해 두었고, 구독 서비스(메커니즘)는 자동으로 모든 구독자에게 알림을 보내고 새 호를 배달합니다.
- 사용 시기: 이벤트 기반 프로그래밍의 기초입니다. GUI 이벤트 리스너(버튼 클릭)부터 데이터 변경 시 UI를 업데이트하는 상태 관리 프레임워크(Redux, Vuex 등)까지 모든 곳에서 사용됩니다.
3. 🏷️ 커맨드 패턴 (Command Pattern)
- 정의: 요청(request)을 요청에 대한 모든 정보를 포함하는 독립형 객체로 변환합니다. 이를 통해 메서드를 다양한 요청으로 매개변수화하고, 요청을 큐에 넣거나 로그로 기록하며, 실행 취소 가능한 작업을 지원할 수 있습니다.
- 비유: 레스토랑 주문. 웨이터("호출자", Invoker)가 주문을 받으면, 이는 커맨드 객체(예: "스테이크 주문" 객체)가 됩니다. 이 객체는 무엇을 해야 할지(
execute()) 그리고 누가 해야 할지(요리사는 "수신자", Receiver)를 알고 있습니다. 웨이터는 그냥 주방에 주문서를 전달합니다. 이제 주방은 이 주문을 큐에 넣거나, 기록하거나, (이론상) "실행 취소"할 수 있습니다. 웨이터는 스테이크 굽는 법을 모르며, 단지 커맨드를 전달하는 방법만 압니다. - 사용 시기: 작업을 큐에 쌓아야 할 때, 실행 취소/재실행 기능을 만들어야 할 때, 또는 요청을 발행하는 객체와 요청을 수행하는 객체를 분리(decouple)해야 할 때 사용합니다.
4. 📝 템플릿 메서드 패턴 (Template Method Pattern)
- 정의: 알고리즘의 "뼈대"를 부모 클래스에 정의하되, 알고리즘의 전체 구조를 변경하지 않으면서 특정 단계들을 서브클래스에서 재정의(override)할 수 있게 합니다.
- 비유: 조립식 주택 건설. 부모 클래스(템플릿)는 '1. 기초 다지기, 2. 벽 세우기, 3. 지붕 얹기, 4. 내부 장식하기'라는 주 알고리즘을 제공합니다.
기초 다지기와벽 세우기단계는 고정되어 변경할 수 없습니다. 하지만ModernHouse나VictorianHouse같은 서브클래스들은지붕 얹기와내부 장식하기단계에 대해 자신들만의 고유한 구현을 제공할 수 있습니다. - 사용 시기: 공통된 구조를 공유하지만 일부 단계에서 약간의 차이가 있는 여러 알고리즘이 있을 때 사용합니다. 상속을 통해 코드 중복을 줄입니다.
5. 🚶♂️ 이터레이터 패턴 (Iterator Pattern)
- 정의: 컬렉션(집합 객체)의 내부 표현을 노출하지 않고, 그 안의 요소들에 순차적으로 접근하는 표준적인 방법을 제공합니다.
- 비유: TV 리모컨. 리모컨("이터레이터")에는
next()와hasNext()(채널 올림/내림) 버튼이 있습니다. 사용자("클라이언트")는 TV의 채널이 리스트로 저장되어 있는지, 트리로 저장되어 있는지, 혹은 다른 복잡한 내부 구조로 되어 있는지 알 필요가 없습니다. 그저 표준 리모컨 인터페이스를 사용해 한 채널에서 다음 채널로 이동할 뿐입니다. - 사용 시기: 이 패턴은 거의 모든 현대 언어(자바스크립트의
for...of루프, 자바/파이썬의 이터레이터 등)에 내장되어 있습니다. 컬렉션의 타입과 관계없이 작동하는 코드를 작성하고 싶을 때 사용합니다.
6. vending-machine 스테이트 패턴 (State Pattern)
- 정의: 객체가 내부 상태가 변할 때 행동을 변경할 수 있게 합니다. 객체는 마치 클래스가 바뀐 것처럼 보입니다.
- 비유: 자판기.
-
NoCoinState(동전 없음 상태)일 때, "구매" 버튼을 눌러도 아무 일도 일어나지 않습니다. -
동전을 넣으면
HasCoinState(동전 있음 상태)로 전환됩니다. -
이제, 이 새로운 상태에서는 "구매" 버튼을 누르면 제품이 나오고 다시 NoCoinState로 전환됩니다.
"구매" 버튼의 행동이 기계의 내부 상태에 따라 완전히 바뀝니다.
-
- 사용 시기: 상태 변수를 확인하는 거대한
if/else또는switch문을 제거하고 싶을 때 사용합니다. 상태별 행동을 별도의 상태 객체로 캡슐화하여 코드를 정리합니다.
기타 주요 행동 패턴
- 책임 연쇄 패턴 (Chain of Responsibility): 요청을 핸들러(handler)의 사슬을 따라 전달합니다. 각 핸들러는 요청을 처리할지, 아니면 체인의 다음 핸들러로 넘길지 결정합니다(예: 입력 필드에 대한 "유효성 검사기" 체인).
- 미디에이터 패턴 (Mediator): 여러 객체 간의 복잡한 의존성을 줄여줍니다. 객체들이 서로 직접 대화하는 대신, 모든 객체가 중앙 "중재자"와 대화하고, 중재자가 적절한 객체로 통신을 리디렉션합니다(예: 공항 관제탑).
- 비지터 패턴 (Visitor): 클래스들을 변경하지 않고도 클래스 집합에 새로운 작업을 추가할 수 있게 합니다(예: 여러 다른
Shape객체 그룹에saveAsXML()작업 추가하기).
references